// TODO: CODE REVIEW & CLEANUP!
//
// MusicAlbumScreen.xaml.cs
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml;
using HomeUX.ClientAppSupport;
using HomeUX.ClientControlSupport;
using HomeUX.Controls;
using HomeUX.Synchronization;
using HomeUX.Utilities;

namespace HomeUX.MusicClientLibrary
{

public partial class MusicAlbumScreen : UserControl
{
    MusicClientHelper _musicClientHelper;
	string _albumId;
	string _artistName;
	string _albumName;
	byte[] _albumArtBytes;
    Dictionary<int, TrackInfo> _tracks = new Dictionary<int, TrackInfo>();
	List<TrackId> _queue = new List<TrackId>();

    public string AlbumId { get; set; } // set by a "caller" before navigating here
    public int HighlightTrackNumber { get; set; } // set by a "caller" before navigating here; -1 to highlight no track

    /// <summary>
    /// If not <n>-1</n>, then the track button with this track number needs to get pseudofocus
    /// when <n>TracksListBox</n> is ready to allow setting of pseudofocus.
    /// </summary>
    int _pseudofocusTrackNumber = -1;

    SyncNode _queueChangeCountNode;

    /// <summary>
    /// A <n>String.Format</n>-style format string to use for <n>HeaderTextBlock</n>.  Initialized
    /// from the design-time text value of  <n>HeaderTextBlock</n>.
    /// </summary>
    string _headerFormatString;

    public MusicAlbumScreen()
    {
        InitializeComponent();
        
        // initialize <_headerFormatString>
        _headerFormatString = HeaderTextBlock.Text;

        Loaded += MusicAlbumScreen_Loaded;
    }

    void MusicAlbumScreen_Loaded(object sender, RoutedEventArgs e)
    {
        _musicClientHelper = new MusicClientHelper(this, BeginLoadingDataFromMusicServer,
            OnParcelReceived);
        _musicClientHelper.LoadingMessage = "Loading album...";
        _musicClientHelper.Screen.Opened += OnScreenOpened;

        // reset screen state if we disconnect, or connect to a different server
        _musicClientHelper.Screen.Reset += delegate
        {
            AlbumId = null;
            HighlightTrackNumber = -1;
            _musicClientHelper.SetErrorState();
        };

        MusicController.SyncPath = _musicClientHelper.MusicDeviceName;

        _queueChangeCountNode = (SyncNode)_musicClientHelper.MusicDeviceNode.CreateNode(
            SyncPath.FromString("QueueChangeCount"));
        _queueChangeCountNode.ValueChanged += _queueChangeCountNode_ValueChanged;
    }

    void OnScreenOpened(object sender, EventArgs e)
    {
        // these properties are set when we receive details about album <AlbumId> from the music
        // server
        MusicController.OverrideAlbumId = null;
        MusicController.OverrideArtistName = null;
        MusicController.OverrideAlbumName = null;
        MusicController.OverrideAlbumArt = null;

        // do the same thing we'd do if we were waiting for the music server to become available
        _musicClientHelper.TryToLoadData();

        // in case <_pseudofocusTrackNumber> was still set from the previous time this screen was
        // opened, reset it now
        _pseudofocusTrackNumber = -1;
    }

    void BeginLoadingDataFromMusicServer()
    {
        // if the required properties were not set correctly before navigating to this screen,
        // this screen is in an error state
        if (AlbumId == null)
        {
            _musicClientHelper.SetErrorState();
            return;
        }

        ScreenShield.Visibility = Visibility.Visible;
        string command = ParsedSyncNodeCommand.Format("GetAlbumDetails",
            "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId),
            "AlbumId", AlbumId);
        _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);

        // while we're loading the "main data" (above), let's also request queue detais
        RequestQueueDetails();
    }

    void _queueChangeCountNode_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        // do nothing if we're in an error state
        if (_musicClientHelper.LoadState == MusicClientLoadState.Error)
            return;

        // the queue changed -- request details
        RequestQueueDetails();
    }

    //bool _requestingQueueDetails;

    void RequestQueueDetails()
    {
        //// do nothing if we're already requesting queue details
        //if (_requestingQueueDetails)
        //    return;

        string command = ParsedSyncNodeCommand.Format("GetQueueDetails",
            "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId));
        _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);
        //_requestingQueueDetails = true;
    }

    Brush _nonHighlightBrush = new SolidColorBrush(Colors.Black);
    Brush _highlightBrush = new SolidColorBrush(Colors.Red);


    void OnParcelReceived(Guid recipientId, XmlReader reader)
    {
        // do nothing if we're in an error state
        if (_musicClientHelper.LoadState == MusicClientLoadState.Error)
            return;

        try
        {
            List<TrackId> newQueue = null;
            foreach (XmlElementInfo element in XmlElementInfo.EnumerateElements(reader))
            {
                switch (element.Name)
                {

                case "AlbumDetails":

                    _albumId = element.GetRequiredAttribute("Id");
                    _artistName = element.GetRequiredAttribute("ArtistName");
                    _albumName = element.GetRequiredAttribute("Name");
                    _albumArtBytes = null;
                    _tracks.Clear();
                    break;

                case "AlbumArt":

                    _albumArtBytes = element.GetContentAsBytes();
                    break;

                case "Track":

                    var newTrack = new TrackInfo()
                    {
                        TrackNumber = element.GetRequiredIntAttribute("TrackNumber"),
                        TrackName = element.GetRequiredAttribute("Name"),
                        Duration = element.GetRequiredDoubleAttribute("Duration"),
                        Year = element.GetRequiredIntAttribute("Year"),
                        Composer = element.GetRequiredAttribute("Composer")
                    };
                    _tracks[newTrack.TrackNumber] = newTrack;
                    break;

                case "/AlbumDetails":

                    HeaderTextBlock.Text = String.Format(_headerFormatString, _albumName,
                        _musicClientHelper.MusicDeviceName);

                    MusicController.OverrideAlbumId = _albumId;
                    MusicController.OverrideArtistName = _artistName;
                    MusicController.OverrideAlbumName = _albumName;
                    MusicController.OverrideAlbumArt = _albumArtBytes;

                    if (_albumId == AlbumId)
                    {
                        TracksListBox.Items.Clear();
                        Style trackTextStyle = (Style)LayoutRoot.Resources["TrackTextStyle"];
                        foreach (TrackInfo track in _tracks.Values)
                        {
                            var textBlock = new TextBlock()
                            {
                                Tag = track.TrackNumber,
                                Text = String.Format("{0}. {1}", track.TrackNumber,
                                    track.TrackName),
                                Style = trackTextStyle
                            };

							if (track.TrackNumber == HighlightTrackNumber)
							{
								textBlock.Foreground = _highlightBrush;
                                _pseudofocusTrackNumber = track.TrackNumber;
							}
							else
								textBlock.Foreground = _nonHighlightBrush;

                            TracksListBox.Items.Add(textBlock);
                        }

                        ScreenShield.Visibility = Visibility.Collapsed;
                        _musicClientHelper.OnDataLoaded();
                        MusicController.UpdateDisplay();
                        UpdateTracksLitState();
                    }
                    else
                    {
                        ScreenShield.Visibility = Visibility.Visible;
                        _musicClientHelper.SetErrorState();
                    }
                    break;

                case "QueueDetails":

                    newQueue = new List<TrackId>();
                    break;

                case "TrackInQueue":

                    newQueue.Add(new TrackId()
                    {
                        AlbumId = element.GetRequiredAttribute("AlbumId"),
                        TrackNumber = element.GetRequiredIntAttribute("TrackNumber"),
                    });
                    break;

                case "/QueueDetails":

                    _queue = newQueue;
                    UpdateTracksLitState();
                    break;

                case "AddedTrackToQueue":
                case "RemovedTrackFromQueue":
                case "DonePlayAlbum":

                    // this was sent in response to an "AddToQueue", "RemoveFromQueue", or
                    // "PlayAlbum" command sent by this screen as a result, we need to hide the
					// waiting indicator on the track button
                    string albumId = element.GetRequiredAttribute("AlbumId");
                    if (albumId == _albumId)
                    {
                        int trackNumber = element.GetRequiredIntAttribute(
							((element.Name == "DonePlayAlbum")
								? "StartAtTrackNumber" : "TrackNumber"));
                        TouchListBoxItem button;
                        TracksListBox.TryMapItemValueToButton(XmlConvert.ToString(trackNumber),
                            out button);
                        button.IsWaiting = false;

                        //if ((_queueAllTracksWaitingOn != null) &&
                        //    (element.Name == "AddedTrackToQueue"))
                        //{
                        //    // the user previously pressed the "Queue All Tracks" button -- we
                        //    // send the request to queue the tracks to the server, and we've been
                        //    // waiting for each track to be queued; see if we're done
                        //    _queueAllTracksWaitingOn.Remove(trackNumber);
                        //    if (_queueAllTracksWaitingOn.Count == 0)
                        //    {
                        //        // "Queue All Tracks" is complete
                        //        _queueAllTracksWaitingOn = null;
                        //        MusicController.QueueAllTracksButtonIsWaiting = false;
                        //    }
                        //}
                    }
                    break;
                }
            }
        }
        catch (InvalidOperationException ex)
        {
            ClientGlobals.Alert(AlertType.Warning,
                "Invalid reply from server: {0}", ex.Message);
        }
    }

    private void MusicController_ViewQueueButtonClick(object sender, EventArgs e)
    {
        _musicClientHelper.GoToMusicScreen("Queue");
    }

    private void MusicController_IsAddToQueueModeChange(object sender, EventArgs e)
    {
        UpdateTracksLitState();
    }

    /*
    /// <summary>
    /// Contains the tracks for which the "Queue All Tracks" button is waiting for an
    /// "AddedTrackToQueue" parcel element.  In other words, when these tracks have been queued,
    /// we can remove the waiting indicator on the "Queue All Tracks" button.  If this field
    /// is <n>null</n>, the waiting indicator isn't displayed.
    /// </summary>
    Dictionary<int, TrackInfo> _queueAllTracksWaitingOn;
    */

    private void MusicController_QueueAllTracksButtonClick(object sender, EventArgs e)
    {
        if (_tracks == null)
            return;
        string trackNumbers = String.Join(",", _tracks.Keys.Select(
            i => XmlConvert.ToString(i)).ToArray());
        
        string command = ParsedSyncNodeCommand.Format("AddToQueue",
            "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId),
            "AlbumId", _albumId, "TrackNumbers", trackNumbers);

        MusicController.QueueAllTracksButtonIsWaiting = true;
        _musicClientHelper.WaitForMusicDeviceNode(delegate
        {
            MusicController.QueueAllTracksButtonIsWaiting = false;
        });

        _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);

        //// see comments for <_queueAllTracksWaitingOn>
        //MusicController.QueueAllTracksButtonIsWaiting = true;
        //_queueAllTracksWaitingOn = new Dictionary<int, TrackInfo>(_tracks);
    }

    private void MusicController_ClearQueueButtonClick(object sender, EventArgs e)
    {
        MusicController.ClearQueueButtonIsWaiting = true;
        _musicClientHelper.WaitForMusicDeviceNode(delegate
        {
            MusicController.ClearQueueButtonIsWaiting = false;
        });
        _musicClientHelper.MusicDeviceNode.ExecuteCommand("ClearQueue");
    }

    void UpdateTracksLitState()
    {
		// set <tracksInQueue> to the collection of track numbers of tracks that are in the queue,
		// or to null if we're not in add-to-queue mode
        Dictionary<int, bool> tracksInQueue = null;
        if (MusicController.IsAddToQueueMode)
        {
			// do nothing if the queue hasn't been downloaded from the music server yet; when it's
			// downloaded, the display will be updated
			if (_queue == null)
				return;

            tracksInQueue = new Dictionary<int, bool>();
            foreach (TrackId trackId in _queue)
            {
                if (trackId.AlbumId == _albumId)
                    tracksInQueue[trackId.TrackNumber] = true;
            }
		}

		// update the lit state of each track button
		foreach (object item in TracksListBox.Items)
		{
			TouchListBoxItem button;
			if (TracksListBox.TryMapItemToButton(item, out button))
			{
                string itemValue = button.ItemValue as string;
                if (itemValue != null)
                {
                    int trackNumber = XmlConvert.ToInt32(itemValue);
                    if (tracksInQueue != null)
                        button.IsLit = tracksInQueue.ContainsKey(trackNumber);
                    else
                    {
                        button.IsLit =
                            (MusicController.CurrentAlbumId == AlbumId) &&
                            (MusicController.CurrentTrackNumber == trackNumber);
                    }
                }
			}
		}
    }

    private void MusicController_BrowseArtistsButtonClick(object sender, EventArgs e)
    {
        _musicClientHelper.GoToMusicScreen("Library");
    }

    private void MusicController_SearchButtonClick(object sender, EventArgs e)
    {
        _musicClientHelper.GoToMusicScreen("Search");
    }

    private void MusicController_ViewThisArtistButtonClick(object sender, EventArgs e)
    {
        string artistName = MusicController.OverrideArtistName;
        if (artistName != null)
        {
            Screen screen;
            MusicArtistScreen screenControl;
            _musicClientHelper.FindMusicScreen("Artist", out screen, out screenControl);
            screenControl.ArtistName = artistName;
            screen.GoTo();
        }
    }

    private void MusicController_ViewThisAlbumButtonClick(object sender, EventArgs e)
    {
        // this button is hidden due to the mode that the MusicController control is in
        // "library" mode i.e. "this album" is always what's shown (by definition)
    }

    private void MusicController_ViewNowPlayingButtonClick(object sender, EventArgs e)
    {
        string albumId = MusicController.CurrentAlbumId;
        if (!String.IsNullOrEmpty(albumId))
			GoToAlbum(albumId);
    }

	/// <summary>
	/// Does the same thing as if the application had set <r>AlbumId</r> to <pr>albumId</pr> and
	/// navigated to this screen, i.e. display information about <pr>albumId</pr>.
	/// </summary>
	void GoToAlbum(string albumId)
	{
		AlbumId = albumId;
		OnScreenOpened(null, null);
	}

    private void TracksListBox_ItemClick(object sender, ItemClickEventArgs e)
    {
        int trackNumber = XmlConvert.ToInt32((string)e.ItemValue);
        TouchListBoxItem button;
        TracksListBox.TryMapItemToButton(e.Item, out button);

        if (MusicController.IsAddToQueueMode)
        {
            // we're in add-to-queue mode: add or remove the clicked-on track to/from the queue
            if (!button.IsLit)
            {
                // add track <trackNumber> to the queue
                string command = ParsedSyncNodeCommand.Format("AddToQueue",
                    "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId),
                    "AlbumId", _albumId, "TrackNumbers", XmlConvert.ToString(trackNumber));
                _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);
                button.IsLit = true;
                button.IsWaiting = true;
            }
            else
            {
                // remove track <trackNumber> from the queue
                string command = ParsedSyncNodeCommand.Format("RemoveFromQueue",
                    "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId),
                    "AlbumId", _albumId, "TrackNumbers", XmlConvert.ToString(trackNumber));
                _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);
                button.IsLit = false;
                button.IsWaiting = true;
            }
        }
        else
        {
            // we're not in add-to-queue mode -- clear the queue and play the album, starting at
			// the clicked-on track
            string command = ParsedSyncNodeCommand.Format("PlayAlbum",
                "RecipientId", XmlConvert.ToString(_musicClientHelper.RecipientId),
                "AlbumId", _albumId, "StartAtTrackNumber", XmlConvert.ToString(trackNumber));
            _musicClientHelper.MusicDeviceNode.ExecuteCommand(command);
            button.IsLit = false;
            button.IsWaiting = true;
        }
    }

    private void MusicController_MustNavigateToNewAlbum(object sender, EventArgs e)
    {
        GoToAlbum(MusicController.CurrentAlbumId);
    }

    private void MusicController_BasicStatusChange(object sender, EventArgs e)
    {
        UpdateTracksLitState();
    }

	class TrackInfo
	{
		public int TrackNumber;
		public string TrackName;
		public double Duration;
		public int Year;
		public string Composer;
	}

    [DebuggerDisplay("{AlbumId} {TrackNumber}")]
	public class TrackId
	{
		public string AlbumId;
		public int TrackNumber;
	}

    private void TracksListBox_LayoutComplete(object sender, EventArgs e)
    {
        UpdateTracksLitState();

        if (_pseudofocusTrackNumber >= 0)
        {
            // set pseudofocus to the tracks list box button associated with track number
            // <_pseudofocusTrackNumber> (if any), primarily to cause it to scroll into view; we
            // can only do this when TracksListBox is in a state to allow pseudofocus to be set
            // (which 
            TouchListBoxItem button;
            if (TracksListBox.TryMapItemValueToButton(_pseudofocusTrackNumber.ToString(), out button))
            {
                TracksListBox.TrySetPseudofocus(button);
                _pseudofocusTrackNumber = -1;
            }
        }
    }
}

}
